from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
# 导入模块json，以便之后能够正确地加载文件中的数据
import json
import requests
import math
import pygal
# 由于需要将数据按月份、周数、周几分组，再计算每组的均值
# 所以导入Python标准库中模块itertools的函数groupby
from itertools import groupby

# 首先导入下载文件使用的模块
# 使用try/except语句实现兼容Python 2.x 和Python 3.x代码
# 最终都会导入 urlopen 函数
try:
    # Python 2.x 版本
    from urllib2 import urlopen
except ImportError:
    # Python 3.x 版本
    from urllib.request import urlopen
json_url = 'http://raw.githubusercontent.com/muxuezi/btc/master/btc_close_2017.json'
# 将网址传入函数
# 执行后,Python就会向GitHub的服务器发送请求
# GitHub服务器响应请求后把btc_close_2017.json文件发送给Python
# 之后用read()读取文件数据
response = urlopen(json_url)
# 读取数据
req = response.read()
# 将数据写入文件
# btc_close_2017_urllib.json 和 btc_close_2017.json的内容是一样的
with open('btc_close_2017_urllib.json', 'wb') as f:
    f.write(req)
# 加载json格式，将文件内容转换成Python能够处理的格式
file_urllib = json.loads(req)
print(file_urllib)

json_url = 'http://raw.githubusercontent.com/muxuezi/btc/master/btc_close_2017.json'
# requests通过get()方法向
req = requests.get(json_url)
# 将数据写入文件GitHub的服务器发送请求
# GitHub服务器响应，返回的结果存储在req变量中
with open('btc_close_2017_request.json', 'w') as f:
    f.write(req.text) # req.text属性可以直接读取文件数据，返回格式是字符串
# 直接用req.json()就可以将btc_close_2017.json文件的数据转换成Python列表file_requests
# 与之前的file_urllib内容相同
file_requests = req.json()
print(file_urllib == file_requests)

# 将数据加载到一个列表中
filename = 'btc_close_2017.json'
with open(filename) as f:
    btc_data = json.load(f)
# 打印每一天的信息
for btc_dict in btc_data:
    # 每个元素都是一个字典，包含五个键-值对
    date = btc_dict['date']
    month = int(btc_dict['month'])
    week = int(btc_dict['week'])
    weekday = btc_dict['weekday']
    # 原始数据的格式经常是不统一的
    # Python不能直接将包含小数点的字符串'6928.6492'转换为整数
    # 故为了消除这种错误，需要先将字符串转换为浮点数，再将浮点数转换为整数
    close = int(float(btc_dict['close']))
    print("{} is month {} week {}, {}, the close price is {} RMB"
          .format(date, month, week, weekday, close))

# 创建5个列表，分别存储日期和收盘价
dates = []
months = []
weeks = []
weekdays = []
close = []
# 每一天的信息
for btc_dict in btc_data:
    dates.append(btc_dict['date'])
    months.append(int(btc_dict['month']))
    weeks.append(int(btc_dict['week']))
    weekdays.append(btc_dict['weekday'])
    close.append(int(float(btc_dict['close'])))

# 对图形进行适当的调整
# x_label_rotation=20：让x轴上的日期标签顺时针旋转20°
# show_minor_x_labels=False：告诉图形不必显示所有的x轴标签
line_chart = pygal.Line(x_label_rotation=20, show_minor_x_labels=False)
line_chart.title = '收盘价对数变换（￥）'
line_chart.x_labels = dates
N = 20 # x轴坐标每隔20天显示一次
line_chart.x_labels_major = dates[::N]
# 需要将非线性的趋势消除，对数变换是常用的处理方法之一
# 这里以10为底的对数函数math.log10计算收盘价，日期仍保持不变。
# 这种方式称为半对数变换
close_log = [math.log10(price) for price in close]
line_chart.add('log收盘价', close_log)
line_chart.render_to_file('收盘价对数变换折线图（￥）.svg')

def draw_line(x_data, y_data, title, y_legend):
    """封装btc_close_2017_2中的绘图代码"""
    xy_map = []

    # --------笔记--------
    # zip() 函数用于将可迭代的对象作为参数，
    # 将对象中对应的元素打包成一个个元组，然后返回由这些元组组成的列表。
    # 如果各个迭代器的元素个数不一致，
    # 则返回列表长度与最短的对象相同，利用 * 号操作符，可以将元组解压为列表。
    # --------笔记--------

    # 将x轴与y轴的数据合并、排序，再用函数groupby()分组
    for x, y in groupby(sorted(zip(x_data, y_data)), key=lambda _: _[0]):
        y_list = [v for _, v in y]
        # 求出每组的均值，存储到xy_map变量中
        xy_map.append([x, sum(y_list) / len(y_list)])
    # 将xy_map中存储的x轴与y轴数据分离
    # x_unique：月份（前11个月） y_mean：月日均值
    x_unique, y_mean = [*zip(*xy_map)]
    # pygal.Line()：利用pygal绘制折线图
    line_chart = pygal.Line()
    line_chart.title = title
    line_chart.x_labels = x_unique
    line_chart.add(y_legend, y_mean)
    line_chart.render_to_file(title+'.svg')
    return line_chart

# 绘制2017年前11个月的日均值
# 由于2017年12月的数据不完整，只取1月到11月的数据。
# 通过dates查找2017-12-01的索引位置，确定周数和收盘价的取数范围
idx_month = dates.index('2017-12-01')
line_chart_month = draw_line(
    months[:idx_month], close[:idx_month], '收盘价月日均值（¥）', '月日均值')
line_chart_month

# 绘制前49周的日均值（2017-01-02~2017-12-10）
idx_week = dates.index('2017-12-11')
line_chart_week = draw_line(
    weeks[1:idx_week], close[1:idx_week], '收盘价周日均值（¥）', '周日均值')
line_chart_week

# 绘制每周中各天的均值
idx_week = dates.index('2017-12-11')
wd = ['Monday', 'Tuesday', 'Wednesday',
      'Thursday', 'Friday', 'Saturday', 'Sunday']
# 将weekdays的内容替换成1~7的整数
weekdays_int = [wd.index(w) + 1 for w in weekdays[1:idx_week]]
line_chart_weekday = draw_line(
    weekdays_int, close[1:idx_week], '收盘价星期均值（¥）', '星期均值')
# 图形生成之后，再将图形的x轴标签替换成中文
line_chart_weekday.x_labels = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
line_chart_weekday.render_to_file('收盘价星期均值（¥）.svg')
line_chart_weekday

# 收盘价数据仪表盘
# 创建一个名为收盘价Dashboard.html的网页文件，然后将每幅图添加到页面中
with open('收盘价Dashboard.html', 'w', encoding='utf8') as html_file:
    html_file.write(
        '<html><head><title>收盘价Dashboard</title><meta charset="utf-8"></head><body>\n')
    for svg in [
            '收盘价折线图（¥）.svg', '收盘价对数变换折线图（¥）.svg', '收盘价月日均值（¥）.svg',
            '收盘价周日均值（¥）.svg', '收盘价星期均值（¥）.svg'
    ]:
        html_file.write(
            '    <object type="image/svg+xml" data="{0}" height=500></object>\n'.format(svg))  # 1
    html_file.write('</body></html>')